#include <fcntl.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <stdlib.h>

#include "soh.h"

/*************************************************************
This file contains functions that listenon a TCP port and 
process HTTP requests. The main task is to provide the minimum
necessary functionality of the web server for sharing files.
**************************************************************/

/*
CR = \r
LF = \n

The line terminator for message-header fields is the sequence CRLF. 
However, we recommend that applications, when parsing such headers, 
recognize a single LF as a line terminator and ignore the leading CR.
*/

/******************************
*      HTTP PARSER UTILS      *
******************************/

/*
0   - end of header
pos - length of header
neg - errno
*/
static int http_read_word( int socket, char* buf, int buflen ){
	
	ssize_t res;
	int byteCounter = 0;
	char* start = buf;
	
l_readNext:
	//receive a byte
	res = recv(socket, buf, 1, MSG_WAITALL);
	if( !res    ) return -ECONNRESET;
	if( res < 0 ){
		if( errno == EAGAIN ) return -ETIMEDOUT;
		return -errno;
	}
	byteCounter++;
	
	//check byte
	if( *buf == ' ' ){
		*buf = 0x00;
		return buf - start;//length
	}else if( *buf == '\r' ) *buf = 0x00;
	else if(  *buf == '\n' ){
		*buf = 0x00;
		//if this header terminator
		if( byteCounter == 1  || byteCounter == 2 ) return 0;
		return buf - start;//length
	}else buf++;
	
	//check the length
	if( byteCounter == buflen ) return -EMSGSIZE;
	
	//read next byte
	goto l_readNext;
}

/*
0   - end of header
pos - length of header
neg - errno
*/
static int httpReadHeader( int socket, char* buf, int buflen ){
	
	ssize_t res;
	int byteCounter = 0;
	char* start = buf;
	
l_readNext:
	//receive a byte
	res = recv(socket, buf, 1, MSG_WAITALL);
	if( !res    ) return -ECONNRESET;
	if( res < 0 ){
		if( errno == EAGAIN ) return -ETIMEDOUT;
		return -errno;
	}
	byteCounter++;
	
	//check byte
	if(      *buf == '\r' ) *buf = 0x00;
	else if( *buf == '\n' ){
		*buf = 0x00;
		//if this header terminator
		if( byteCounter == 1  || byteCounter == 2 ) return 0;
		return buf - start;//length
	}else buf++;
	
	//check the length
	if( byteCounter == buflen ) return -EMSGSIZE;//Message too long
	
	//read next byte
	goto l_readNext;
}

static int http_parser( struct instance* this ){
	
	int res;
	char buf[4096];
	
	//method
	res = http_read_word( this->clientSocket, this->method, sizeof(this->method) );
	if( res < 0 ){
		printf( "Parse HTTP method error: %s\n", strerror(-res) );
		return res;
	}
	
	//uri
	res = http_read_word( this->clientSocket, this->uri, sizeof(this->uri) );
	if( res < 0 ){
		printf( "Parse HTTP uri error: %s\n", strerror(-res) );
		return res;
	}
	
	//TODO: HTTP 0.9 has not this field
	//protocol
	res = http_read_word( this->clientSocket, this->protocol, sizeof(this->protocol) );
	if( res < 0 ){
		printf( "Parse HTTP protocol error: %s\n", strerror(-res) );
		return res;
	}
	
	//headers
	do{
		res = httpReadHeader( this->clientSocket, buf, sizeof(buf) );
	}while( res > 0 );
	
	if( res < 0 ) printf( "Parse HTTP headers error: %s\n", strerror(-res) );
	
	return res;
}

int httpSendStatus( struct instance* this, int statusCode, char* statusText ){
	
	int res;
	char buf[128];
	
	//create header
	res = sprintf( buf, "%s %i %s\r\n\r\n", this->protocol, statusCode, statusText );
	
	//send header
	res = sendData( this->clientSocket, buf, res );
	
	if( !res ) printf( "<<< %i %s\n", statusCode, statusText );
	
	return res;
}

static int www_server( struct instance* this ){
	
	int res;
	char tmpbuf[32];
	size_t uriOffset;
	
	uriOffset = 1;
	
	//receive and parse HTTP headers
	res = http_parser( this );
	if( res ) return -1;
	
	printf( "method: %s, uri: %s, protocol: %s\n", this->method, this->uri, this->protocol );
	
	//check method
	if( strcmp(this->method, "GET") ) return httpSendStatus( this, 501, "Method not implemented." );
	
	/*********   parse uri and handle request   *********/
	if( this->uri[0] != '/' ) return httpSendStatus( this, 400, "Bad request." );
	
	//shareId or favicon
	res = parserCore( this->uri, '/', tmpbuf, sizeof(tmpbuf), &uriOffset );
	if( res < 0 )                       return httpSendStatus( this, 400, "Bad request." );
	if( !res    )                       return httpSendStatus( this, 404, "Not found."   );
	if( strcmp(tmpbuf, this->shareId) ) return httpSendStatus( this, 404, "Not found."   );
	
	//request( shareinfo, nodeinfo etc. )
	res = parserCore( this->uri, '/', tmpbuf, sizeof(tmpbuf), &uriOffset );
	if( res < 0 ) return httpSendStatus( this, 400, "Bad request." );
	if( !res ){
		if(      !strcmp(tmpbuf, "shareinfo") ) return shareBrowserHtml( this );                            
	} else {
		if(      !strcmp(tmpbuf, "nodeinfo" ) ) return loadNodesBackend( this, uriOffset );
		else if( !strcmp(tmpbuf, "download" ) ) return htmlDownloadHandler( this, uriOffset );
	}
	
	return httpSendStatus( this, 403, "Access denied." );
}

int startServer( struct instance* this, unsigned short listenPort ){
	
	const int reuseAddrEnable = 1;
	
	int res;
	int serverSocket;
	struct sockaddr_in addr;
	struct timeval socketTimeout;
	
	//TODO: SIGPIPE
	
	//set timeout value
	socketTimeout.tv_sec = this->netTimeout;
	socketTimeout.tv_usec = 0;
	
	//socket
	serverSocket = socket( AF_INET, SOCK_STREAM, 0 );
	if( serverSocket < 0 ){
		perror("socket");
		return -1;
	}
	
	//set SO_REUSEADDR option for server socket
	res =  setsockopt( serverSocket, SOL_SOCKET, SO_REUSEADDR, &reuseAddrEnable, sizeof(reuseAddrEnable) );
	if( res ){
		perror( "server sock set SO_REUSEADDR" );
		return -1;
	}
	
	//set close-on-exec flag on server socket
	res = fcntl( serverSocket, F_SETFD, FD_CLOEXEC );
	if( res ){
		perror( "set close-on-exec flag on server socket" );
		return -1;
	}
	
	addr.sin_family = AF_INET;
	addr.sin_port = htons( listenPort );
	addr.sin_addr.s_addr = htonl( INADDR_ANY );
	
	//bind
	res = bind( serverSocket, (struct sockaddr *)&addr, sizeof(addr) );
	if( res ){
		perror( "bind" );
		return -1;
	}
	
	//listen
	res = listen( serverSocket, 10 );
	if( res ){
		perror( "listen" );
		return -1;
	}
	
	printf( "Listening %i TCP...\n", listenPort );
	
	//accept
l_accept:
	this->clientSocket = accept( serverSocket, NULL, NULL );
	if( this->clientSocket > 0 ){
		
		//connect indicator
		printf( ">>> " );
		fflush( stdout );
		
		//set SO_RCVTIMEO for client socket
		res = setsockopt( this->clientSocket , SOL_SOCKET, SO_RCVTIMEO, &socketTimeout, sizeof(socketTimeout) );
		if( res ){
			perror( "client sock set SO_RCVTIMEO" );
			goto l_closeClient;
		}
		
		//set SO_SNDTIMEO for client socket
		res = setsockopt( this->clientSocket , SOL_SOCKET, SO_SNDTIMEO, &socketTimeout, sizeof(socketTimeout) );
		if( res ){
			perror( "client sock set SO_SNDTIMEO" );
			goto l_closeClient;
		}
		
		//set close-on-exec flag on client socket
		res = fcntl( this->clientSocket, F_SETFD, FD_CLOEXEC );
		if( res ){
			perror( "set close-on-exec flag on client socket" );
			goto l_closeClient;
		}
		
		//handle request
		res = www_server( this );
		
l_closeClient:
		//close client socket
		close( this->clientSocket );
		
		//accept next
		if( res > -2 ) goto l_accept;
	} else {
		res = -1;
		perror( "accept" );
	}
	
	close( serverSocket );
	return res;
}
